/*
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 */
#include <linux/generic_block.h>
#include <system/filesystem.h>
#include <system/system.h>

struct gendisk *gendisk_head = NULL;

static int current_minor = 0;
extern int *blk_size[];

extern void rd_load(void);
extern int ramdisk_size;

/*
 * Create devices for each logical partition in an extended partition.
 * The logical partitions form a linked list, with each entry being
 * a partition table with two entries.  The first entry
 * is the real data partition (with a start relative to the partition
 * table start).  The second is a pointer to the next logical partition
 * (with a start relative to the entire extended partition).
 * We do not create a Linux partition for the partition tables, but
 * only for the actual data partitions.
 */
static void extended_partition(struct gendisk *hd, int dev) {

	struct buffer_head *bh;
	struct partition *p;
	unsigned long first_sector, this_sector;
	int mask = (1 << hd->minor_shift) - 1;

	first_sector = hd->part[MINOR(dev)].start_sect;
	this_sector = first_sector;

	while (1) {

		if ((current_minor & mask) >= (4 + hd->max_p))
			return;

		// TODO
		//if (!(bh = bread(dev,0,1024))) {
		//	return;
		//}

		/*
		 * This block is from a device that we're about to stomp on.
		 * So make sure nobody thinks this block is usable.
		 */
		bh->b_dirt = 0;
		bh->b_uptodate = 0;
		bh->b_req = 0;
		if (*(unsigned short *) (bh->b_data+510) == 0xAA55) {
			p = (struct partition *) (0x1BE + bh->b_data);
		/*
		 * Process the first entry, which should be the real
		 * data partition.
		 */
			if (p->sys_ind == EXTENDED_PARTITION ||
			    !(hd->part[current_minor].nr_sects = p->nr_sects))
				goto done;  /* shouldn't happen */
			hd->part[current_minor].start_sect = this_sector + p->start_sect;
			printk(" %s%c%d", hd->major_name,
				'a'+(current_minor >> hd->minor_shift),
				mask & current_minor);
			current_minor++;
			p++;
		/*
		 * Process the second entry, which should be a link
		 * to the next logical partition.  Create a minor
		 * for this just long enough to get the next partition
		 * table.  The minor will be reused for the real
		 * data partition.
		 */
			if (p->sys_ind != EXTENDED_PARTITION ||
			    !(hd->part[current_minor].nr_sects = p->nr_sects))
				goto done;  /* no more logicals in this partition */
			hd->part[current_minor].start_sect = first_sector + p->start_sect;
			this_sector = first_sector + p->start_sect;
			dev = ((hd->major) << 8) | current_minor;

			// TODO:
			//brelse(bh);
		} else
			goto done;
	}
done:
	4;
	// TODO:
	//brelse(bh);
}

static void check_partition(struct gendisk *hd, unsigned int dev) {
	static int first_time = 1;
	int i, minor = current_minor;
	struct buffer_head *bh;
	struct partition *p;
	unsigned long first_sector;
	int mask = (1 << hd->minor_shift) - 1;

	if (first_time)
		printk("Partition check:\n");
	first_time = 0;
	first_sector = hd->part[MINOR(dev)].start_sect;
	
	// TODO:
	//if (!(bh = bread(dev,0,1024))) {
	//	printk("  unable to read partition table of device %04x\n",dev);
	//	return;
	//}

	printk("  %s%c:", hd->major_name, 'a'+(minor >> hd->minor_shift));
	current_minor += 4;  /* first "extra" minor */
	if (*(unsigned short *) (bh->b_data+510) == 0xAA55) {
		p = (struct partition *) (0x1BE + bh->b_data);
		for (i=1 ; i<=4 ; minor++,i++,p++) {
			if (!(hd->part[minor].nr_sects = p->nr_sects))
				continue;
			hd->part[minor].start_sect = first_sector + p->start_sect;
			printk(" %s%c%d", hd->major_name,'a'+(minor >> hd->minor_shift), i);
			if ((current_minor & 0x3f) >= 60)
				continue;
			if (p->sys_ind == EXTENDED_PARTITION) {
				printk(" <");
				extended_partition(hd, (hd->major << 8) | minor);
				printk(" >");
			}
		}
		/*
		 * check for Disk Manager partition table
		 */
		if (*(unsigned short *) (bh->b_data+0xfc) == 0x55AA) {
			p = (struct partition *) (0x1BE + bh->b_data);
			for (i = 4 ; i < 16 ; i++, current_minor++) {
				p--;
				if ((current_minor & mask) >= mask-2)
					break;
				if (!(p->start_sect && p->nr_sects))
					continue;
				hd->part[current_minor].start_sect = p->start_sect;
				hd->part[current_minor].nr_sects = p->nr_sects;
				printk(" %s%c%d", hd->major_name,
					'a'+(current_minor >> hd->minor_shift),
					current_minor & mask);
			}
		}
	} else
		printk(" bad partition table");
	printk("\n");

	// TODO
	//brelse(bh);
}

/**
 * This function will re-read the partition tables for a given device,
 * and set things back up again.  There are some important caveats,
 * however.  You must ensure that no one is using the device, and no one
 * can start using the device while this function is being executed. 
 */
void resetup_one_dev(struct gendisk *dev, int drive) {
	int i;
	int start = drive<<dev->minor_shift;
	int j = start + dev->max_p;
	int major = dev->major << 8;

	current_minor = 1+(drive<<dev->minor_shift);
	check_partition(dev, major+(drive<<dev->minor_shift));

	for (i=start ; i < j ; i++)
		dev->sizes[i] = dev->part[i].nr_sects >> (BLOCK_SIZE_BITS - 9);
}

static void setup_dev(struct gendisk *dev) {

	int i;
	int j = dev->max_nr * dev->max_p;
	int major = dev->major << 8;
	int drive;
	
	for (i = 0 ; i < j; i++)  {
		dev->part[i].start_sect = 0;
		dev->part[i].nr_sects = 0;
	}
	dev->init();
	for (drive = 0; drive < dev->nr_real; drive++) {
		current_minor = 1+(drive<<dev->minor_shift);
		check_partition(dev, major + (drive<<dev->minor_shift));
	}
	for (i=0 ; i < j ; i++) {
		dev->sizes[i] = dev->part[i].nr_sects >> (BLOCK_SIZE_BITS - 9);
	}
	blk_size[dev->major] = dev->sizes;
}
	
void device_setup(void) {
	struct gendisk *p;
	int nr = 0;

	for (p = gendisk_head ; p ; p=p->next) {
		setup_dev(p);
		nr += p->nr_real;
	}		

	// TODO:
	//if (ramdisk_size) {
	//	rd_load();
	//}
}
